package com.cskaoyan.javase.object._3equals;

/**
 * Object类当中的equals方法
 * 1.方法的声明
 *       public boolean equals(Object obj)
 * 没有特别需要注意的,返回值是一个布尔类型,需要一个Object参数,说明该方法可以传入所有的对象
 *
 * 2.方法的作用
 *      equals有相等的意思,该方法是用来判断两个对象是否相等(方法的调用者对象和方法传入的对象比较)
 *      既然是比较相等,就需要一个比较的规则,这个方法的具体实现(方法体)就是表示一种比较相等的规则
 *
 * 3.Object当中默认实现的比较规则,Object类当中是如何决定对象相等的.
 *      默认实现的方法体:  return (this == obj);
 *  所以默认情况下,只有当两个引用完全指向同一个对象时,对象才相等
 *  这个条件是很苛刻,说白了就是对象自己才和自己相等.
 *  在大多数情况下,我们不需要这么苛刻的条件,所以需要重写类中的equals方法
 *
 *  4.重写equals方法的对象相等规则
 *      我们认为什么时候对象相等?
 *          1.当两个对象是完全的同种类型对象时,属性取值只要相等,对象就相等
 *          2.当两个对象不是同种类型对象时,没有可比性,直接判断不等.
 *
 *  5.重写equals方法的常规协定(JDK文档中,提供了重写该方法的原则)
 *
 *       1. 自反性：对于任何非空引用值 x，`x.equals(x) `都应返回 true
 *       自己和自己比较,应该无条件返回true
 *
 *       2. 排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *      如果传入的对象不是同种类型对象或者干脆就不是一个对象,是一个null,直接认定不相等,返回false
 *
 *       3. 对称性：对于任何非空引用值 x 和 y，当且仅当` y.equals(x)` 返回 true 时，x.equals(y) 才应返回true
 *       如果x和y相等,那么不管谁来调用equals方法
 *       结果都应该是true
 *
 *       4. 传递性：对于任何非空引用值 x、y 和 z，如果` x.equals(y) `返回 true，并且 `y.equals(z)` 返回 true，那么`x.equals(z)` 应返回 true。
 *       如果x和y相等,y和z相等
 *       那么x就应该和z相等
 *
 *       5. 一致性：对于任何非空引用值 x 和 y，多次调用 `x.equals(y) `始终返回 true 或始终返回 false。
 *       只要条件不变,不管调用多少次equals方法
 *       结果不变
 *
 * 如果用取值来比较对象相等,那么它自动满足对称性,传递性,一致性
 * 于是我们恰好用成员变量的取值来判断对象相等,对称性,传递性,一致性自动满足
 *
 *
 * 6.equals方法使用细节问题:
 *      a.equals方法的重写可以用alt + insert快捷键生成
 *
 *      b.浮点数包括double,float,它们是比较特殊的,存在无穷大,无穷小,非数(NaN)等等概念,所以比较浮点数的大小,一般情况下用"=="是不会出错的.但是为了严谨起见,我们用包装类中的compare方法比较两个浮点数的大小.
 *      Float.compare(float,float)
 *      Double.compare(double,double)
 * 该方法的返回值:
 *      1.如果返回值大于0,说明前面的参数大,后面的参数小
 *      2.如果返回值小于0,说明前面的参数小,后面的参数大
 *      3.如果返回值等于0.说明两个参数相等
 *
 *      c.排它性实现的两种方式
 *          1.getClass,严格判断是否同一个类型,只要不是同种类型,就直接返回false,不关心是不是子类.
 *          2.instanceof
 *          引用 instanceof 类名/接口名
 *          判断引用所指向的对象是否是后面类名的对象或者子类对象
 *          用这种方式判断排它性,允许子类传入equals方法
 *          一般情况下,还是getClass用的多一点
 *          当然具体情况,具体分析
 *
 *      d.浮点数由于存在精度问题,所以它不能直接用于财务运算等高精度的运算场景
 *          推荐使用BigDecimal类来完成小数的运算.
 *
 *      e.如果类中存在引用数据类型成员变量，那么就需要重写该类的equals方法，否则仍然用默认Object中的equals方法实现。
 *
 *
 * @since 11:07
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        /*Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        // 默认情况下,equals比较地址,所以返回false
        // 如果重写equals方法,用成员变量的取值判断对象相等,结果就是true
        System.out.println(s1.equals(s2));

        Star star = new Star();
        System.out.println(s1.equals(star));*/

        Student s1 = new Student(18, 100);
        StudentSon s2 = new StudentSon(18, 100);
        System.out.println(s1.equals(s2));
    }
}

class Student {
    int age;
    double score;
    Star star;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    public Student() {
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return star != null ? star.equals(student.star) : student.star == null;
    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return star.equals(student.star);
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (star != null ? star.hashCode() : 0);
        return result;
    }

    // @Override
    // public boolean equals(Object o) {
    //     if (this == o) return true;
    //     if (!(o instanceof Student)) return false;
    //
    //     Student student = (Student) o;
    //
    //     if (age != student.age) return false;
    //     return Double.compare(student.score, score) == 0;
    // }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }*/

    /*@Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }*/

    /* @Override
    public boolean equals(Object obj) {
        // 1.满足自反性
        if (this == obj) {
            return true;
        }
        // 2.满足排它性
        *//*
            使用getClass方法获取对象的运行时类对象
            只有相同类型的对象,它的运行时对象才是同一个

            并且要先判断obj是否为null,避免空指针异常
          *//*
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        *//*
            用成员变量的取值来判断对象相等,对称性,传递性,一致性自动满足
         *//*
        // 接下来比较两个对象的成员变量取值即可
        // obj是一个父类引用,需要强转成子类引用才可以判断取值
        // 思考: 做强转需要instanceof判断,这里还需要判断吗?  不需要
        // 因为代码运行到这里,obj对象一定是Student对象,不需要再做判断了,可以直接强转
        Student target = (Student) obj;
        if (this.age != target.age){
            return false;
        }
        // 代码运行到这里,age一定相等,只需要判断score相等就是equals方法的结果
        return this.score == target.score;
    }*/
}

class Star {
    double salary;

    public Star() {
    }

    public Star(double salary) {
        this.salary = salary;
    }
}

class StudentSon extends Student {
    public StudentSon(int age, double score) {
        super(age, score);
    }

    public StudentSon() {
    }
}